redux-observable を TypeScript で使う
何の影響かは分からないが、rxjs/operators/filter が Type Guard してくれていた
code:typeguard.ts
action$.pipe(
filter(actionCreator.match),
map(action => { /* ここで action の型が Payload も含めて推論されている */ })
)
という訳で、以下は読まなくて大丈夫です
経緯
ActionCreator が Payload の型を持っている → reducer で型推論できる!やったね!
こうなったら redux-observable の Epic を書くときにも型推論が欲しいな・・・ filter は Type Guard を持っていないので、 filter + Type Guard みたいなオペレータがあれば良いのでは?
ofActionメソッドをObservableに追加することで、上記の型推論を実現している
これは突破口見えたか!?teramotodaiki.icon
npm i -S typescript-fsa-redux-observable
import しようとしたが、何も export されていない😵
よく見たら RxJS 5 で作られている😨
今回使っているのは RxJS 6
5 → 6 の breaking changes の一つとして、prototype メソッドがコアから削除された
まさにprototype.ofActionを追加する実装であったため、何も起こらなかったということ
対処法は2つある
もう一つは、ofActionを pipable operator の形で書き直す方法
そんなに作業量は多くないだろうということでこちらを選択した
結果として半日溶けた(teramotodaiki.icon RxJS を知らなさすぎる)😭
teramotodaiki.icon 余談だけど、必死に英語で書いて送ったら author さんも日本人だった
code:typescript-fsa-redux-observable.ts
import { Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Action, ActionCreator } from 'typescript-fsa';
export const ofAction = <Payload>(actionCreator: ActionCreator<Payload>) => (
actions$: Observable<Action<Payload>>
) => actions$.pipe(filter(actionCreator.match));
実装してみたteramotodaiki.icon
Type Guard は?
actionCreator.match に Type Guard が含まれているので、ここには書いてない
使い方は?
ofAction(actionCreator)
ofAction(asyncActionCreators.started)
ofAction(asyncActionCreators.failed)
ofAction(asyncActionCreators.done)
をpipe に入れて使う
https://gyazo.com/b4a3f34cf462307dd7e2bc42b1e475e1
combineEpics 付近で型エラー起こすんだけど?
redux-observable の型定義がなんかザルなんですよね〜
ReturnType<ActionObservable<T>['pipe']> === Observable<T>
要するに「Action 以外も流すよ!」と言ってる🤔
本当はこうなっててほしい:ReturnType<ActionObservable<T>['pipe']> === Observable<Action<T>>
でも、ここでいう Action って何???てなる
redux の定義する Action と typescript-fsa なんかが定義してる Action は微妙に異なる
色んなサードパーティ製ライブラリと一緒に使おうと思ったら、なんでも流せる型にせざるを得なかった?teramotodaiki.icon
対処法
combineEpics のジェネリクスに型を代入する
code:epics.ts
import { combineEpics, Epic } from 'redux-observable';
import { Action } from 'typescript-fsa';
const epics = combineEpics<Epic<Action<any>, Action<any>, StoreState>>(
/* ...Epics */
)
StoreState って何?
Root State の型のことです
これをどっかで定義してないと、 redux を TypeScript で使うのはしんどいと思いますteramotodaiki.icon
なんで any にしちゃうの?
Epic にはアプリケーション内のあらゆる Action が流れ込んでくるので、こうせざるを得ないのですteramotodaiki.icon
どうせofAction を使えば型の恩恵を受けられるし、ofAction を使わない Epic を書くべきではない(無限ループ怖い)ので、これで良しとしましたteramotodaiki.icon
code:redux-observable/index.d.ts
export declare interface Epic<
Input extends Action = any,
Output extends Input = Input,
State = any,
Dependencies = any> {
(action$: ActionsObservable<Input>, state$: StateObservable<State>, dependencies: Dependencies): Observable<Output>;
}
ざっくり言うと Observable => Observable という形をしている
rxjs の operator と似ている
pipe の中にそのまま入れられる